/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
import { mxgraph } from '..';
import { useGraph } from './Graph';
import { Base64 } from 'js-base64';
let EditorInfo;
export function useEditor(options) {
  if (!EditorInfo) {
    EditorInfo = buildEditor(options);
  }
  return EditorInfo;
}

function buildEditor({ ChangePageSetup }) {
  const {
    mxGraph,
    mxEventSource,
    mxUtils,
    mxClient,
    mxPopupMenu,
    mxConnectionHandler,
    mxEvent,
    mxEventObject,
    mxConstants,
    mxRectangle,
    mxResources,
    mxPoint,
    mxCodec,
    mxUndoManager,
    mxChildChange,
    mxDivResizer,
    mxPrintPreview,
    mxGraphView,
    mxMouseEvent,
    mxPolyline,
    mxGraphHandler,
    mxCellMarker,
    mxRectangleShape,
    mxPopupMenuHandler,
    urlParams,
    IMAGE_PATH,
  } = mxgraph;

  /**
   * Editor constructor executed on page load.
   */
  const Editor = function (chromeless, themes, model, graph, editable) {
    mxEventSource.call(this);
    this.chromeless = chromeless != null ? chromeless : this.chromeless;
    this.initStencilRegistry();
    this.graph = graph || this.createGraph(themes, model);
    this.editable = editable != null ? editable : !chromeless;
    this.undoManager = this.createUndoManager();
    this.status = '';

    this.getOrCreateFilename = function () {
      return this.filename || mxResources.get('drawing', [Editor.pageCounter]) + '.xml';
    };

    this.getFilename = function () {
      return this.filename;
    };

    // Sets the status and fires a statusChanged event
    this.setStatus = function (value) {
      this.status = value;
      this.fireEvent(new mxEventObject('statusChanged'));
    };

    // Returns the current status
    this.getStatus = function () {
      return this.status;
    };

    // Updates modified state if graph changes
    this.graphChangeListener = function (sender, eventObject) {
      var edit = eventObject != null ? eventObject.getProperty('edit') : null;

      if (edit == null || !edit.ignoreEdit) {
        this.setModified(true);
      }
    };

    this.graph.getModel().addListener(
      mxEvent.CHANGE,
      mxUtils.bind(this, function () {
        this.graphChangeListener.apply(this, arguments);
      }),
    );

    // Sets persistent graph state defaults
    this.graph.resetViewOnRootChange = false;
    this.init();
  };

  const { Graph, HoverIcons } = useGraph({ Editor });
  /**
   * Counts open editor tabs (must be global for cross-window access)
   */
  Editor.pageCounter = 0;

  // Cross-domain window access is not allowed in FF, so if we
  // were opened from another domain then this will fail.
  (function () {
    try {
      var op = window;

      while (
        op.opener != null &&
        typeof op.opener.Editor !== 'undefined' &&
        !isNaN(op.opener.Editor.pageCounter) &&
        // Workaround for possible infinite loop in FF https://drawio.atlassian.net/browse/DS-795
        op.opener != op
      ) {
        op = op.opener;
      }

      // Increments the counter in the first opener in the chain
      if (op != null) {
        op.Editor.pageCounter++;
        Editor.pageCounter = op.Editor.pageCounter;
      }
    } catch (e) {
      // ignore
    }
  })();

  /**
   * Specifies if local storage should be used (eg. on the iPad which has no filesystem)
   */
  Editor.useLocalStorage = typeof Storage != 'undefined' && mxClient.IS_IOS;

  /**
   *
   */
  Editor.moveImage = mxClient.IS_SVG
    ? ''
    : IMAGE_PATH + '/move.png';

  /**
   *
   */
  Editor.rowMoveImage = mxClient.IS_SVG
    ? ''
    : IMAGE_PATH + '/thumb_horz.png';

  /**
   * Images below are for lightbox and embedding toolbars.
   */
  Editor.helpImage = mxClient.IS_SVG
    ? ''
    : IMAGE_PATH + '/help.png';

  /**
   * Sets the default font size.
   */
  Editor.checkmarkImage = mxClient.IS_SVG
    ? ''
    : IMAGE_PATH + '/checkmark.gif';

  /**
   * Images below are for lightbox and embedding toolbars.
   */
  Editor.maximizeImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.zoomOutImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.zoomInImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.zoomFitImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.layersImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.previousImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.nextImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.editImage = mxClient.IS_SVG
    ? ''
    : IMAGE_PATH + '/edit.gif';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.zoomOutLargeImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.zoomInLargeImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.actualSizeLargeImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.printLargeImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.layersLargeImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.closeLargeImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.editLargeImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.previousLargeImage =
    '';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.nextLargeImage =
    '';

  /**
   * Specifies the image to be used for the refresh button.
   */
  Editor.refreshLargeImage =
    '';

  /**
   * Specifies the image to be used for the back button.
   */
  Editor.backLargeImage =
    '';

  /**
   * Specifies the image to be used for the back button.
   */
  Editor.fullscreenLargeImage =
    '';

  /**
   * All fill styles supported by rough.js.
   */
  Editor.roughFillStyles = [
    { val: 'auto', dispName: 'Auto' },
    { val: 'hachure', dispName: 'Hachure' },
    { val: 'solid', dispName: 'Solid' },
    { val: 'zigzag', dispName: 'ZigZag' },
    { val: 'cross-hatch', dispName: 'Cross Hatch' },
    { val: 'dots', dispName: 'Dots' },
    { val: 'dashed', dispName: 'Dashed' },
    { val: 'zigzag-line', dispName: 'ZigZag Line' },
  ];

  /**
   * Graph themes for the format panel.
   */
  Editor.themes = null;

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.ctrlKey = mxClient.IS_MAC ? 'Cmd' : 'Ctrl';

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.hintOffset = 20;

  /**
   * Specifies if the diagram should be saved automatically if possible. Default
   * is true.
   */
  Editor.popupsAllowed = true;

  /**
   * Editor inherits from mxEventSource
   */
  mxUtils.extend(Editor, mxEventSource);

  /**
   * Stores initial state of mxClient.NO_FO.
   */
  Editor.prototype.originalNoForeignObject = mxClient.NO_FO;

  /**
   * Specifies the image URL to be used for the transparent background.
   */
  Editor.prototype.transparentImage = mxClient.IS_SVG
    ? ''
    : IMAGE_PATH + '/transparent.gif';

  /**
   * Specifies if the canvas should be extended in all directions. Default is true.
   */
  Editor.prototype.extendCanvas = true;

  /**
   * Specifies if the app should run in chromeless mode. Default is false.
   * This default is only used if the contructor argument is null.
   */
  Editor.prototype.chromeless = false;

  /**
   * Specifies the order of OK/Cancel buttons in dialogs. Default is true.
   * Cancel first is used on Macs, Windows/Confluence uses cancel last.
   */
  Editor.prototype.cancelFirst = true;

  /**
   * Specifies if the editor is enabled. Default is true.
   */
  Editor.prototype.enabled = true;

  /**
   * Contains the name which was used for the last save. Default value is null.
   */
  Editor.prototype.filename = null;

  /**
   * Contains the current modified state of the diagram. This is false for
   * new diagrams and after the diagram was saved.
   */
  Editor.prototype.modified = false;

  /**
   * Specifies if the diagram should be saved automatically if possible. Default
   * is true.
   */
  Editor.prototype.autosave = true;

  /**
   * Specifies the top spacing for the initial page view. Default is 0.
   */
  Editor.prototype.initialTopSpacing = 0;

  /**
   * Specifies the app name. Default is document.title.
   */
  Editor.prototype.appName = document.title;

  /**
   *
   */
  Editor.prototype.editBlankUrl = window.location.protocol + '//' + window.location.host + '/';

  /**
   * Default value for the graph container overflow style.
   */
  Editor.prototype.defaultGraphOverflow = 'hidden';

  /**
   * Initializes the environment.
   */
  Editor.prototype.init = function () {};

  /**
   * Sets the XML node for the current diagram.
   */
  Editor.prototype.isChromelessView = function () {
    return this.chromeless;
  };

  /**
   * Sets the XML node for the current diagram.
   */
  Editor.prototype.setAutosave = function (value) {
    this.autosave = value;
    this.fireEvent(new mxEventObject('autosaveChanged'));
  };

  /**
   *
   */
  Editor.prototype.getEditBlankUrl = function (params) {
    return this.editBlankUrl + params;
  };

  /**
   *
   */
  Editor.prototype.editAsNew = function (xml, title) {
    var p = title != null ? '?title=' + encodeURIComponent(title) : '';

    if (urlParams['ui'] != null) {
      p += (p.length > 0 ? '&' : '?') + 'ui=' + urlParams['ui'];
    }

    if (
      typeof window.postMessage !== 'undefined' &&
      (document.documentMode == null || document.documentMode >= 10)
    ) {
      var wnd = null;

      var l = mxUtils.bind(this, function (evt) {
        if (evt.data == 'ready' && evt.source == wnd) {
          mxEvent.removeListener(window, 'message', l);
          wnd.postMessage(xml, '*');
        }
      });

      mxEvent.addListener(window, 'message', l);
      wnd = this.graph.openLink(
        this.getEditBlankUrl(p + (p.length > 0 ? '&' : '?') + 'client=1'),
        null,
        true,
      );
    } else {
      this.graph.openLink(this.getEditBlankUrl(p) + '#R' + encodeURIComponent(xml));
    }
  };

  /**
   * Sets the XML node for the current diagram.
   */
  Editor.prototype.createGraph = function (themes, model) {
    var graph = new Graph(null, model, null, null, themes);
    graph.transparentBackground = false;

    // Opens all links in a new window while editing
    if (!this.chromeless) {
      graph.isBlankLink = function (href) {
        return !this.isExternalProtocol(href);
      };
    }

    return graph;
  };

  /**
   * Sets the XML node for the current diagram.
   */
  Editor.prototype.resetGraph = function () {
    this.graph.gridEnabled = !this.isChromelessView() || urlParams['grid'] == '1';
    this.graph.graphHandler.guidesEnabled = true;
    this.graph.setTooltips(true);
    this.graph.setConnectable(true);
    this.graph.foldingEnabled = true;
    this.graph.scrollbars = this.graph.defaultScrollbars;
    this.graph.pageVisible = this.graph.defaultPageVisible;
    this.graph.pageBreaksVisible = this.graph.pageVisible;
    this.graph.preferPageSize = this.graph.pageBreaksVisible;
    this.graph.background = null;
    this.graph.pageScale = mxGraph.prototype.pageScale;
    this.graph.pageFormat = mxGraph.prototype.pageFormat;
    this.graph.currentScale = 1;
    this.graph.currentTranslate.x = 0;
    this.graph.currentTranslate.y = 0;
    this.updateGraphComponents();
    this.graph.view.setScale(1);
  };

  /**
   * Sets the XML node for the current diagram.
   */
  Editor.prototype.readGraphState = function (node) {
    this.graph.gridEnabled =
      node.getAttribute('grid') != '0' && (!this.isChromelessView() || urlParams['grid'] == '1');
    this.graph.gridSize = parseFloat(node.getAttribute('gridSize')) || mxGraph.prototype.gridSize;
    this.graph.graphHandler.guidesEnabled = node.getAttribute('guides') != '0';
    this.graph.setTooltips(node.getAttribute('tooltips') != '0');
    this.graph.setConnectable(node.getAttribute('connect') != '0');
    this.graph.connectionArrowsEnabled = node.getAttribute('arrows') != '0';
    this.graph.foldingEnabled = node.getAttribute('fold') != '0';

    if (this.isChromelessView() && this.graph.foldingEnabled) {
      this.graph.foldingEnabled = urlParams['nav'] == '1';
      this.graph.cellRenderer.forceControlClickHandler = this.graph.foldingEnabled;
    }

    var ps = parseFloat(node.getAttribute('pageScale'));

    if (!isNaN(ps) && ps > 0) {
      this.graph.pageScale = ps;
    } else {
      this.graph.pageScale = mxGraph.prototype.pageScale;
    }

    if (!this.graph.isLightboxView() && !this.graph.isViewer()) {
      var pv = node.getAttribute('page');

      if (pv != null) {
        this.graph.pageVisible = pv != '0';
      } else {
        this.graph.pageVisible = this.graph.defaultPageVisible;
      }
    } else {
      this.graph.pageVisible = false;
    }

    this.graph.pageBreaksVisible = this.graph.pageVisible;
    this.graph.preferPageSize = this.graph.pageBreaksVisible;

    var pw = parseFloat(node.getAttribute('pageWidth'));
    var ph = parseFloat(node.getAttribute('pageHeight'));

    if (!isNaN(pw) && !isNaN(ph)) {
      this.graph.pageFormat = new mxRectangle(0, 0, pw, ph);
    }

    // Loads the persistent state settings
    var bg = node.getAttribute('background');

    if (bg != null && bg.length > 0) {
      this.graph.background = bg;
    } else {
      this.graph.background = null;
    }
  };

  /**
   * Sets the XML node for the current diagram.
   */
  Editor.prototype.setGraphXml = function (node) {
    if (node != null) {
      var dec = new mxCodec(node.ownerDocument);

      if (node.nodeName == 'mxGraphModel') {
        this.graph.model.beginUpdate();

        try {
          this.graph.model.clear();
          this.graph.view.scale = 1;
          this.readGraphState(node);
          this.updateGraphComponents();
          dec.decode(node, this.graph.getModel());
        } finally {
          this.graph.model.endUpdate();
        }

        this.fireEvent(new mxEventObject('resetGraphView'));
      } else if (node.nodeName == 'root') {
        this.resetGraph();

        // Workaround for invalid XML output in Firefox 20 due to bug in mxUtils.getXml
        var wrapper = dec.document.createElement('mxGraphModel');
        wrapper.appendChild(node);

        dec.decode(wrapper, this.graph.getModel());
        this.updateGraphComponents();
        this.fireEvent(new mxEventObject('resetGraphView'));
      } else {
        throw {
          message: mxResources.get('cannotOpenFile'),
          node: node,
          toString: function () {
            return this.message;
          },
        };
      }
    } else {
      this.resetGraph();
      this.graph.model.clear();
      this.fireEvent(new mxEventObject('resetGraphView'));
    }
  };

  /**
   * Returns the XML node that represents the current diagram.
   */
  Editor.prototype.getGraphXml = function (ignoreSelection) {
    ignoreSelection = ignoreSelection != null ? ignoreSelection : true;
    var node = null;

    if (ignoreSelection) {
      var enc = new mxCodec(mxUtils.createXmlDocument());
      node = enc.encode(this.graph.getModel());
    } else {
      node = this.graph.encodeCells(
        mxUtils.sortCells(this.graph.model.getTopmostCells(this.graph.getSelectionCells())),
      );
    }

    if (this.graph.view.translate.x != 0 || this.graph.view.translate.y != 0) {
      node.setAttribute('dx', Math.round(this.graph.view.translate.x * 100) / 100);
      node.setAttribute('dy', Math.round(this.graph.view.translate.y * 100) / 100);
    }

    node.setAttribute('grid', this.graph.isGridEnabled() ? '1' : '0');
    node.setAttribute('gridSize', this.graph.gridSize);
    node.setAttribute('guides', this.graph.graphHandler.guidesEnabled ? '1' : '0');
    node.setAttribute('tooltips', this.graph.tooltipHandler.isEnabled() ? '1' : '0');
    node.setAttribute('connect', this.graph.connectionHandler.isEnabled() ? '1' : '0');
    node.setAttribute('arrows', this.graph.connectionArrowsEnabled ? '1' : '0');
    node.setAttribute('fold', this.graph.foldingEnabled ? '1' : '0');
    node.setAttribute('page', this.graph.pageVisible ? '1' : '0');
    node.setAttribute('pageScale', this.graph.pageScale);
    node.setAttribute('pageWidth', this.graph.pageFormat.width);
    node.setAttribute('pageHeight', this.graph.pageFormat.height);

    if (this.graph.background != null) {
      node.setAttribute('background', this.graph.background);
    }

    return node;
  };

  /**
   * Keeps the graph container in sync with the persistent graph state
   */
  Editor.prototype.updateGraphComponents = function () {
    var graph = this.graph;

    if (graph.container != null) {
      graph.view.validateBackground();
      graph.container.style.overflow = graph.scrollbars ? 'auto' : this.defaultGraphOverflow;

      this.fireEvent(new mxEventObject('updateGraphComponents'));
    }
  };

  /**
   * Sets the modified flag.
   */
  Editor.prototype.setModified = function (value) {
    this.modified = value;
  };

  /**
   * Sets the filename.
   */
  Editor.prototype.setFilename = function (value) {
    this.filename = value;
  };

  /**
   * Creates and returns a new undo manager.
   */
  Editor.prototype.createUndoManager = function () {
    var graph = this.graph;
    var undoMgr = new mxUndoManager();

    this.undoListener = function (sender, evt) {
      undoMgr.undoableEditHappened(evt.getProperty('edit'));
    };

    // Installs the command history
    var listener = mxUtils.bind(this, function (sender, evt) {
      this.undoListener.apply(this, arguments);
    });

    graph.getModel().addListener(mxEvent.UNDO, listener);
    graph.getView().addListener(mxEvent.UNDO, listener);

    // Keeps the selection in sync with the history
    var undoHandler = function (sender, evt) {
      var cand = graph.getSelectionCellsForChanges(
        evt.getProperty('edit').changes,
        function (change) {
          // Only selects changes to the cell hierarchy
          return !(change instanceof mxChildChange);
        },
      );

      if (cand.length > 0) {
        var model = graph.getModel();
        var cells = [];

        for (var i = 0; i < cand.length; i++) {
          if (graph.view.getState(cand[i]) != null) {
            cells.push(cand[i]);
          }
        }

        graph.setSelectionCells(cells);
      }
    };

    undoMgr.addListener(mxEvent.UNDO, undoHandler);
    undoMgr.addListener(mxEvent.REDO, undoHandler);

    return undoMgr;
  };

  /**
   * Adds basic stencil set (no namespace).
   */
  Editor.prototype.initStencilRegistry = function () {};

  /**
   * Creates and returns a new undo manager.
   */
  Editor.prototype.destroy = function () {
    if (this.graph != null) {
      this.graph.destroy();
      this.graph = null;
    }
  };

  /**
   * Class for asynchronously opening a new window and loading a file at the same
   * time. This acts as a bridge between the open dialog and the new editor.
   */
  const OpenFile = function (done) {
    this.producer = null;
    this.consumer = null;
    this.done = done;
    this.args = null;
  };

  /**
   * Registers the editor from the new window.
   */
  OpenFile.prototype.setConsumer = function (value) {
    this.consumer = value;
    this.execute();
  };

  /**
   * Sets the data from the loaded file.
   */
  OpenFile.prototype.setData = function () {
    this.args = arguments;
    this.execute();
  };

  /**
   * Displays an error message.
   */
  OpenFile.prototype.error = function (msg) {
    this.cancel(true);
    mxUtils.alert(msg);
  };

  /**
   * Consumes the data.
   */
  OpenFile.prototype.execute = function () {
    if (this.consumer != null && this.args != null) {
      this.cancel(false);
      this.consumer.apply(this, this.args);
    }
  };

  /**
   * Cancels the operation.
   */
  OpenFile.prototype.cancel = function (cancel) {
    if (this.done != null) {
      this.done(cancel != null ? cancel : true);
    }
  };

  /**
   * Basic dialogs that are available in the viewer (print dialog).
   */
  function Dialog(
    editorUi,
    elt,
    w,
    h,
    modal,
    closable,
    onClose,
    noScroll,
    transparent,
    onResize,
    ignoreBgClick,
  ) {
    var dx = 0;

    if (mxClient.IS_VML && (document.documentMode == null || document.documentMode < 8)) {
      // Adds padding as a workaround for box model in older IE versions
      // This needs to match the total padding of geDialog in CSS
      dx = 80;
    }

    w += dx;
    h += dx;

    var w0 = w;
    var h0 = h;

    var ds = mxUtils.getDocumentSize();

    // Workaround for print dialog offset in viewer lightbox
    if (window.innerHeight != null) {
      ds.height = window.innerHeight;
    }

    var dh = ds.height;
    var left = Math.max(1, Math.round((ds.width - w - 64) / 2));
    var top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));

    // Keeps window size inside available space
    if (!mxClient.IS_QUIRKS) {
      elt.style.maxHeight = '100%';
    }

    w = document.body != null ? Math.min(w, document.body.scrollWidth - 64) : w;
    h = Math.min(h, dh - 64);

    // Increments zIndex to put subdialogs and background over existing dialogs and background
    if (editorUi.dialogs.length > 0) {
      this.zIndex += editorUi.dialogs.length * 2;
    }

    if (this.bg == null) {
      this.bg = editorUi.createDiv('background');
      this.bg.style.position = 'absolute';
      this.bg.style.background = Dialog.backdropColor;
      this.bg.style.height = dh + 'px';
      this.bg.style.right = '0px';
      this.bg.style.zIndex = this.zIndex - 2;

      mxUtils.setOpacity(this.bg, this.bgOpacity);

      if (mxClient.IS_QUIRKS) {
        new mxDivResizer(this.bg);
      }
    }

    var origin = mxUtils.getDocumentScrollOrigin(document);
    this.bg.style.left = origin.x + 'px';
    this.bg.style.top = origin.y + 'px';
    left += origin.x;
    top += origin.y;

    if (modal) {
      document.body.appendChild(this.bg);
    }

    var div = editorUi.createDiv(transparent ? 'geTransDialog' : 'geDialog');
    var pos = this.getPosition(left, top, w, h);
    left = pos.x;
    top = pos.y;

    div.style.width = w + 'px';
    div.style.height = h + 'px';
    div.style.left = left + 'px';
    div.style.top = top + 'px';
    div.style.zIndex = this.zIndex;

    div.appendChild(elt);
    document.body.appendChild(div);

    // Adds vertical scrollbars if needed
    if (!noScroll && elt.clientHeight > div.clientHeight - 64) {
      elt.style.overflowY = 'auto';
    }

    if (closable) {
      var img = document.createElement('img');

      img.setAttribute('src', Dialog.prototype.closeImage);
      img.setAttribute('title', mxResources.get('close'));
      img.className = 'geDialogClose';
      img.style.top = top + 14 + 'px';
      img.style.left = left + w + 38 - dx + 'px';
      img.style.zIndex = this.zIndex;

      mxEvent.addListener(
        img,
        'click',
        mxUtils.bind(this, function () {
          editorUi.hideDialog(true);
        }),
      );

      document.body.appendChild(img);
      this.dialogImg = img;

      if (!ignoreBgClick) {
        var mouseDownSeen = false;

        mxEvent.addGestureListeners(
          this.bg,
          mxUtils.bind(this, function (evt) {
            mouseDownSeen = true;
          }),
          null,
          mxUtils.bind(this, function (evt) {
            if (mouseDownSeen) {
              editorUi.hideDialog(true);
              mouseDownSeen = false;
            }
          }),
        );
      }
    }

    this.resizeListener = mxUtils.bind(this, function () {
      if (onResize != null) {
        var newWH = onResize();

        if (newWH != null) {
          w0 = w = newWH.w;
          h0 = h = newWH.h;
        }
      }

      var ds = mxUtils.getDocumentSize();
      dh = ds.height;
      this.bg.style.height = dh + 'px';

      left = Math.max(1, Math.round((ds.width - w - 64) / 2));
      top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));
      w = document.body != null ? Math.min(w0, document.body.scrollWidth - 64) : w0;
      h = Math.min(h0, dh - 64);

      var pos = this.getPosition(left, top, w, h);
      left = pos.x;
      top = pos.y;

      div.style.left = left + 'px';
      div.style.top = top + 'px';
      div.style.width = w + 'px';
      div.style.height = h + 'px';

      // Adds vertical scrollbars if needed
      if (!noScroll && elt.clientHeight > div.clientHeight - 64) {
        elt.style.overflowY = 'auto';
      }

      if (this.dialogImg != null) {
        this.dialogImg.style.top = top + 14 + 'px';
        this.dialogImg.style.left = left + w + 38 - dx + 'px';
      }
    });

    mxEvent.addListener(window, 'resize', this.resizeListener);

    this.onDialogClose = onClose;
    this.container = div;

    editorUi.editor.fireEvent(new mxEventObject('showDialog'));
  }

  /**
   *
   */
  Dialog.backdropColor = 'white';

  /**
   *
   */
  Dialog.prototype.zIndex = mxPopupMenu.prototype.zIndex - 1;

  /**
   *
   */
  Dialog.prototype.noColorImage = !mxClient.IS_SVG
    ? IMAGE_PATH + '/nocolor.png'
    : '';

  /**
   *
   */
  Dialog.prototype.closeImage = !mxClient.IS_SVG
    ? IMAGE_PATH + '/close.png'
    : '';

  /**
   *
   */
  Dialog.prototype.clearImage = !mxClient.IS_SVG
    ? IMAGE_PATH + '/clear.gif'
    : '';

  /**
   *
   */
  Dialog.prototype.lockedImage = !mxClient.IS_SVG
    ? IMAGE_PATH + '/locked.png'
    : '';

  /**
   *
   */
  Dialog.prototype.unlockedImage = !mxClient.IS_SVG
    ? IMAGE_PATH + '/unlocked.png'
    : '';

  /**
   * Removes the dialog from the DOM.
   */
  Dialog.prototype.bgOpacity = 80;

  /**
   * Removes the dialog from the DOM.
   */
  Dialog.prototype.getPosition = function (left, top) {
    return new mxPoint(left, top);
  };

  /**
   * Removes the dialog from the DOM.
   */
  Dialog.prototype.close = function (cancel, isEsc) {
    if (this.onDialogClose != null) {
      if (this.onDialogClose(cancel, isEsc) == false) {
        return false;
      }

      this.onDialogClose = null;
    }

    if (this.dialogImg != null) {
      this.dialogImg.parentNode.removeChild(this.dialogImg);
      this.dialogImg = null;
    }

    if (this.bg != null && this.bg.parentNode != null) {
      this.bg.parentNode.removeChild(this.bg);
    }

    mxEvent.removeListener(window, 'resize', this.resizeListener);
    this.container.parentNode.removeChild(this.container);
  };

  /**
   *
   */
  var ErrorDialog = function (
    editorUi,
    title,
    message,
    buttonText,
    fn,
    retry,
    buttonText2,
    fn2,
    hide,
    buttonText3,
    fn3,
  ) {
    hide = hide != null ? hide : true;

    var div = document.createElement('div');
    div.style.textAlign = 'center';

    if (title != null) {
      var hd = document.createElement('div');
      hd.style.padding = '0px';
      hd.style.margin = '0px';
      hd.style.fontSize = '18px';
      hd.style.paddingBottom = '16px';
      hd.style.marginBottom = '10px';
      hd.style.borderBottom = '1px solid #c0c0c0';
      hd.style.color = 'gray';
      hd.style.whiteSpace = 'nowrap';
      hd.style.textOverflow = 'ellipsis';
      hd.style.overflow = 'hidden';
      mxUtils.write(hd, title);
      hd.setAttribute('title', title);
      div.appendChild(hd);
    }

    var p2 = document.createElement('div');
    p2.style.lineHeight = '1.2em';
    p2.style.padding = '6px';
    p2.innerHTML = message;
    div.appendChild(p2);

    var btns = document.createElement('div');
    btns.style.marginTop = '12px';
    btns.style.textAlign = 'center';

    if (retry != null) {
      var retryBtn = mxUtils.button(mxResources.get('tryAgain'), function () {
        editorUi.hideDialog();
        retry();
      });
      retryBtn.className = 'geBtn';
      btns.appendChild(retryBtn);

      btns.style.textAlign = 'center';
    }

    if (buttonText3 != null) {
      var btn3 = mxUtils.button(buttonText3, function () {
        if (fn3 != null) {
          fn3();
        }
      });

      btn3.className = 'geBtn';
      btns.appendChild(btn3);
    }

    var btn = mxUtils.button(buttonText, function () {
      if (hide) {
        editorUi.hideDialog();
      }

      if (fn != null) {
        fn();
      }
    });

    btn.className = 'geBtn';
    btns.appendChild(btn);

    if (buttonText2 != null) {
      var mainBtn = mxUtils.button(buttonText2, function () {
        if (hide) {
          editorUi.hideDialog();
        }

        if (fn2 != null) {
          fn2();
        }
      });

      mainBtn.className = 'geBtn gePrimaryBtn';
      btns.appendChild(mainBtn);
    }

    this.init = function () {
      btn.focus();
    };

    div.appendChild(btns);

    this.container = div;
  };

  /**
   * Constructs a new print dialog.
   */
  var PrintDialog = function (editorUi, title) {
    this.create(editorUi, title);
  };

  /**
   * Constructs a new print dialog.
   */
  PrintDialog.prototype.create = function (editorUi) {
    var graph = editorUi.editor.graph;
    var row, td;

    var table = document.createElement('table');
    table.style.width = '100%';
    table.style.height = '100%';
    var tbody = document.createElement('tbody');

    row = document.createElement('tr');

    var onePageCheckBox = document.createElement('input');
    onePageCheckBox.setAttribute('type', 'checkbox');
    td = document.createElement('td');
    td.setAttribute('colspan', '2');
    td.style.fontSize = '10pt';
    td.appendChild(onePageCheckBox);

    var span = document.createElement('span');
    mxUtils.write(span, ' ' + mxResources.get('fitPage'));
    td.appendChild(span);

    mxEvent.addListener(span, 'click', function (evt) {
      onePageCheckBox.checked = !onePageCheckBox.checked;
      pageCountCheckBox.checked = !onePageCheckBox.checked;
      mxEvent.consume(evt);
    });

    mxEvent.addListener(onePageCheckBox, 'change', function () {
      pageCountCheckBox.checked = !onePageCheckBox.checked;
    });

    row.appendChild(td);
    tbody.appendChild(row);

    row = row.cloneNode(false);

    var pageCountCheckBox = document.createElement('input');
    pageCountCheckBox.setAttribute('type', 'checkbox');
    td = document.createElement('td');
    td.style.fontSize = '10pt';
    td.appendChild(pageCountCheckBox);

    span = document.createElement('span');
    mxUtils.write(span, ' ' + mxResources.get('posterPrint') + ':');
    td.appendChild(span);

    mxEvent.addListener(span, 'click', function (evt) {
      pageCountCheckBox.checked = !pageCountCheckBox.checked;
      onePageCheckBox.checked = !pageCountCheckBox.checked;
      mxEvent.consume(evt);
    });

    row.appendChild(td);

    var pageCountInput = document.createElement('input');
    pageCountInput.setAttribute('value', '1');
    pageCountInput.setAttribute('type', 'number');
    pageCountInput.setAttribute('min', '1');
    pageCountInput.setAttribute('size', '4');
    pageCountInput.setAttribute('disabled', 'disabled');
    pageCountInput.style.width = '50px';

    td = document.createElement('td');
    td.style.fontSize = '10pt';
    td.appendChild(pageCountInput);
    mxUtils.write(td, ' ' + mxResources.get('pages') + ' (max)');
    row.appendChild(td);
    tbody.appendChild(row);

    mxEvent.addListener(pageCountCheckBox, 'change', function () {
      if (pageCountCheckBox.checked) {
        pageCountInput.removeAttribute('disabled');
      } else {
        pageCountInput.setAttribute('disabled', 'disabled');
      }

      onePageCheckBox.checked = !pageCountCheckBox.checked;
    });

    row = row.cloneNode(false);

    td = document.createElement('td');
    mxUtils.write(td, mxResources.get('pageScale') + ':');
    row.appendChild(td);

    td = document.createElement('td');
    var pageScaleInput = document.createElement('input');
    pageScaleInput.setAttribute('value', '100 %');
    pageScaleInput.setAttribute('size', '5');
    pageScaleInput.style.width = '50px';

    td.appendChild(pageScaleInput);
    row.appendChild(td);
    tbody.appendChild(row);

    row = document.createElement('tr');
    td = document.createElement('td');
    td.colSpan = 2;
    td.style.paddingTop = '20px';
    td.setAttribute('align', 'right');

    // Overall scale for print-out to account for print borders in dialogs etc
    this.preview = function preview(print) {
      var autoOrigin = onePageCheckBox.checked || pageCountCheckBox.checked;
      var printScale = parseInt(pageScaleInput.value) / 100;

      if (isNaN(printScale)) {
        printScale = 1;
        pageScaleInput.value = '100%';
      }

      // Workaround to match available paper size in actual print output
      printScale *= 0.75;

      var pf = graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT;
      var scale = 1 / graph.pageScale;

      if (autoOrigin) {
        var pageCount = onePageCheckBox.checked ? 1 : parseInt(pageCountInput.value);

        if (!isNaN(pageCount)) {
          scale = mxUtils.getScaleForPageCount(pageCount, graph, pf);
        }
      }

      // Negative coordinates are cropped or shifted if page visible
      var gb = graph.getGraphBounds();
      var border = 0;
      var x0 = 0;
      var y0 = 0;

      // Applies print scale
      pf = mxRectangle.fromRectangle(pf);
      pf.width = Math.ceil(pf.width * printScale);
      pf.height = Math.ceil(pf.height * printScale);
      scale *= printScale;

      // Starts at first visible page
      if (!autoOrigin && graph.pageVisible) {
        var layout = graph.getPageLayout();
        x0 -= layout.x * pf.width;
        y0 -= layout.y * pf.height;
      } else {
        autoOrigin = true;
      }

      var preview = PrintDialog.createPrintPreview(graph, scale, pf, border, x0, y0, autoOrigin);
      preview.open();

      if (print) {
        PrintDialog.printPreview(preview);
      }
    }

    var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
      editorUi.hideDialog();
    });
    cancelBtn.className = 'geBtn';

    if (editorUi.editor.cancelFirst) {
      td.appendChild(cancelBtn);
    }

    if (PrintDialog.previewEnabled) {
      var previewBtn = mxUtils.button(mxResources.get('preview'), function () {
        editorUi.hideDialog();
        preview(false);
      });
      previewBtn.className = 'geBtn';
      td.appendChild(previewBtn);
    }

    var printBtn = mxUtils.button(
      mxResources.get(!PrintDialog.previewEnabled ? 'ok' : 'print'),
      function () {
        editorUi.hideDialog();
        preview(true);
      },
    );
    printBtn.className = 'geBtn gePrimaryBtn';
    td.appendChild(printBtn);

    if (!editorUi.editor.cancelFirst) {
      td.appendChild(cancelBtn);
    }

    row.appendChild(td);
    tbody.appendChild(row);

    table.appendChild(tbody);
    this.container = table;
  };

  /**
   * Constructs a new print dialog.
   */
  PrintDialog.printPreview = function (preview) {
    try {
      if (preview.wnd != null) {
        var printFn = function () {
          preview.wnd.focus();
          preview.wnd.print();
          preview.wnd.close();
        };

        // Workaround for Google Chrome which needs a bit of a
        // delay in order to render the SVG contents
        // Needs testing in production
        if (mxClient.IS_GC) {
          window.setTimeout(printFn, 500);
        } else {
          printFn();
        }
      }
    } catch (e) {
      // ignores possible Access Denied
    }
  };

  /**
   * Constructs a new print dialog.
   */
  PrintDialog.createPrintPreview = function (graph, scale, pf, border, x0, y0, autoOrigin) {
    var preview = new mxPrintPreview(graph, scale, pf, border, x0, y0);
    preview.title = mxResources.get('preview');
    preview.printBackgroundImage = true;
    preview.autoOrigin = autoOrigin;
    var bg = graph.background;
    if (bg == null || bg == '' || bg == mxConstants.NONE) {
      bg = '#ffffff';
    }

    preview.backgroundColor = bg;

    var writeHead = preview.writeHead;

    // Adds a border in the preview
    preview.writeHead = function (doc) {
      writeHead.apply(this, arguments);

      doc.writeln('<style type="text/css">');
      doc.writeln('@media screen {');
      doc.writeln('  body > div { padding:30px;box-sizing:content-box; }');
      doc.writeln('}');
      doc.writeln('</style>');
    };

    return preview;
  };

  /**
   * Specifies if the preview button should be enabled. Default is true.
   */
  PrintDialog.previewEnabled = true;

  /**
   * Constructs a new page setup dialog.
   */
  var PageSetupDialog = function (editorUi) {
    var graph = editorUi.editor.graph;
    var row, td;

    var table = document.createElement('table');
    table.style.width = '100%';
    table.style.height = '100%';
    var tbody = document.createElement('tbody');

    row = document.createElement('tr');

    td = document.createElement('td');
    td.style.verticalAlign = 'top';
    td.style.fontSize = '10pt';
    mxUtils.write(td, mxResources.get('paperSize') + ':');

    row.appendChild(td);

    td = document.createElement('td');
    td.style.verticalAlign = 'top';
    td.style.fontSize = '10pt';

    var accessor = PageSetupDialog.addPageFormatPanel(td, 'pagesetupdialog', graph.pageFormat);

    row.appendChild(td);
    tbody.appendChild(row);

    row = document.createElement('tr');

    td = document.createElement('td');
    mxUtils.write(td, mxResources.get('background') + ':');

    row.appendChild(td);

    td = document.createElement('td');
    td.style.whiteSpace = 'nowrap';

    var backgroundInput = document.createElement('input');
    backgroundInput.setAttribute('type', 'text');
    var backgroundButton = document.createElement('button');

    backgroundButton.style.width = '18px';
    backgroundButton.style.height = '18px';
    backgroundButton.style.marginRight = '20px';
    backgroundButton.style.backgroundPosition = 'center center';
    backgroundButton.style.backgroundRepeat = 'no-repeat';

    var newBackgroundColor = graph.background;

    function updateBackgroundColor() {
      if (newBackgroundColor == null || newBackgroundColor == mxConstants.NONE) {
        backgroundButton.style.backgroundColor = '';
        backgroundButton.style.backgroundImage = "url('" + Dialog.prototype.noColorImage + "')";
      } else {
        backgroundButton.style.backgroundColor = newBackgroundColor;
        backgroundButton.style.backgroundImage = '';
      }
    }

    updateBackgroundColor();

    mxEvent.addListener(backgroundButton, 'click', function (evt) {
      editorUi.pickColor(newBackgroundColor || 'none', function (color) {
        newBackgroundColor = color;
        updateBackgroundColor();
      });
      mxEvent.consume(evt);
    });

    td.appendChild(backgroundButton);

    mxUtils.write(td, mxResources.get('gridSize') + ':');

    var gridSizeInput = document.createElement('input');
    gridSizeInput.setAttribute('type', 'number');
    gridSizeInput.setAttribute('min', '0');
    gridSizeInput.style.width = '40px';
    gridSizeInput.style.marginLeft = '6px';

    gridSizeInput.value = graph.getGridSize();
    td.appendChild(gridSizeInput);

    mxEvent.addListener(gridSizeInput, 'change', function () {
      var value = parseInt(gridSizeInput.value);
      gridSizeInput.value = Math.max(1, isNaN(value) ? graph.getGridSize() : value);
    });

    row.appendChild(td);
    tbody.appendChild(row);

    row = document.createElement('tr');
    td = document.createElement('td');

    mxUtils.write(td, mxResources.get('image') + ':');

    row.appendChild(td);
    td = document.createElement('td');

    var changeImageLink = document.createElement('a');
    changeImageLink.style.textDecoration = 'underline';
    changeImageLink.style.cursor = 'pointer';
    changeImageLink.style.color = '#a0a0a0';

    var newBackgroundImage = graph.backgroundImage;

    function updateBackgroundImage() {
      if (newBackgroundImage == null) {
        changeImageLink.removeAttribute('title');
        changeImageLink.style.fontSize = '';
        changeImageLink.innerHTML = mxUtils.htmlEntities(mxResources.get('change')) + '...';
      } else {
        changeImageLink.setAttribute('title', newBackgroundImage.src);
        changeImageLink.style.fontSize = '11px';
        changeImageLink.innerHTML =
          mxUtils.htmlEntities(newBackgroundImage.src.substring(0, 42)) + '...';
      }
    }

    mxEvent.addListener(changeImageLink, 'click', function (evt) {
      editorUi.showBackgroundImageDialog(function (image, failed) {
        if (!failed) {
          newBackgroundImage = image;
          updateBackgroundImage();
        }
      }, newBackgroundImage);

      mxEvent.consume(evt);
    });

    updateBackgroundImage();

    td.appendChild(changeImageLink);

    row.appendChild(td);
    tbody.appendChild(row);

    row = document.createElement('tr');
    td = document.createElement('td');
    td.colSpan = 2;
    td.style.paddingTop = '16px';
    td.setAttribute('align', 'right');

    var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
      editorUi.hideDialog();
    });
    cancelBtn.className = 'geBtn';

    if (editorUi.editor.cancelFirst) {
      td.appendChild(cancelBtn);
    }

    var applyBtn = mxUtils.button(mxResources.get('apply'), function () {
      editorUi.hideDialog();
      var gridSize = parseInt(gridSizeInput.value);

      if (!isNaN(gridSize) && graph.gridSize !== gridSize) {
        graph.setGridSize(gridSize);
      }

      var change = new ChangePageSetup(
        editorUi,
        newBackgroundColor,
        newBackgroundImage,
        accessor.get(),
      );
      change.ignoreColor = graph.background == newBackgroundColor;

      var oldSrc = graph.backgroundImage != null ? graph.backgroundImage.src : null;
      var newSrc = newBackgroundImage != null ? newBackgroundImage.src : null;

      change.ignoreImage = oldSrc === newSrc;

      if (
        graph.pageFormat.width != change.previousFormat.width ||
        graph.pageFormat.height != change.previousFormat.height ||
        !change.ignoreColor ||
        !change.ignoreImage
      ) {
        graph.model.execute(change);
      }
    });
    applyBtn.className = 'geBtn gePrimaryBtn';
    td.appendChild(applyBtn);

    if (!editorUi.editor.cancelFirst) {
      td.appendChild(cancelBtn);
    }

    row.appendChild(td);
    tbody.appendChild(row);

    table.appendChild(tbody);
    this.container = table;
  };

  /**
   *
   */
  PageSetupDialog.addPageFormatPanel = function (div, namePostfix, pageFormat, pageFormatListener) {
    var formatName = 'format-' + namePostfix;

    var portraitCheckBox = document.createElement('input');
    portraitCheckBox.setAttribute('name', formatName);
    portraitCheckBox.setAttribute('type', 'radio');
    portraitCheckBox.setAttribute('value', 'portrait');

    var landscapeCheckBox = document.createElement('input');
    landscapeCheckBox.setAttribute('name', formatName);
    landscapeCheckBox.setAttribute('type', 'radio');
    landscapeCheckBox.setAttribute('value', 'landscape');

    var paperSizeSelect = document.createElement('select');
    paperSizeSelect.style.marginBottom = '8px';
    paperSizeSelect.style.width = '202px';

    var formatDiv = document.createElement('div');
    formatDiv.style.marginLeft = '4px';
    formatDiv.style.width = '210px';
    formatDiv.style.height = '24px';

    portraitCheckBox.style.marginRight = '6px';
    formatDiv.appendChild(portraitCheckBox);

    var portraitSpan = document.createElement('span');
    portraitSpan.style.maxWidth = '100px';
    mxUtils.write(portraitSpan, mxResources.get('portrait'));
    formatDiv.appendChild(portraitSpan);

    landscapeCheckBox.style.marginLeft = '10px';
    landscapeCheckBox.style.marginRight = '6px';
    formatDiv.appendChild(landscapeCheckBox);

    var landscapeSpan = document.createElement('span');
    landscapeSpan.style.width = '100px';
    mxUtils.write(landscapeSpan, mxResources.get('landscape'));
    formatDiv.appendChild(landscapeSpan);

    var customDiv = document.createElement('div');
    customDiv.style.marginLeft = '4px';
    customDiv.style.width = '210px';
    customDiv.style.height = '24px';

    var widthInput = document.createElement('input');
    widthInput.setAttribute('size', '7');
    widthInput.style.textAlign = 'right';
    customDiv.appendChild(widthInput);
    mxUtils.write(customDiv, ' in x ');

    var heightInput = document.createElement('input');
    heightInput.setAttribute('size', '7');
    heightInput.style.textAlign = 'right';
    customDiv.appendChild(heightInput);
    mxUtils.write(customDiv, ' in');

    formatDiv.style.display = 'none';
    customDiv.style.display = 'none';

    var pf = new Object();
    var formats = PageSetupDialog.getFormats();

    for (var i = 0; i < formats.length; i++) {
      var f = formats[i];
      pf[f.key] = f;

      var paperSizeOption = document.createElement('option');
      paperSizeOption.setAttribute('value', f.key);
      mxUtils.write(paperSizeOption, f.title);
      paperSizeSelect.appendChild(paperSizeOption);
    }

    var customSize = false;

    function listener(sender, evt, force) {
      if (
        force ||
        (widthInput != document.activeElement && heightInput != document.activeElement)
      ) {
        var detected = false;

        for (var i = 0; i < formats.length; i++) {
          var f = formats[i];

          // Special case where custom was chosen
          if (customSize) {
            if (f.key == 'custom') {
              paperSizeSelect.value = f.key;
              customSize = false;
            }
          } else if (f.format != null) {
            // Fixes wrong values for previous A4 and A5 page sizes
            if (f.key == 'a4') {
              if (pageFormat.width == 826) {
                pageFormat = mxRectangle.fromRectangle(pageFormat);
                pageFormat.width = 827;
              } else if (pageFormat.height == 826) {
                pageFormat = mxRectangle.fromRectangle(pageFormat);
                pageFormat.height = 827;
              }
            } else if (f.key == 'a5') {
              if (pageFormat.width == 584) {
                pageFormat = mxRectangle.fromRectangle(pageFormat);
                pageFormat.width = 583;
              } else if (pageFormat.height == 584) {
                pageFormat = mxRectangle.fromRectangle(pageFormat);
                pageFormat.height = 583;
              }
            }

            if (pageFormat.width == f.format.width && pageFormat.height == f.format.height) {
              paperSizeSelect.value = f.key;
              portraitCheckBox.setAttribute('checked', 'checked');
              portraitCheckBox.defaultChecked = true;
              portraitCheckBox.checked = true;
              landscapeCheckBox.removeAttribute('checked');
              landscapeCheckBox.defaultChecked = false;
              landscapeCheckBox.checked = false;
              detected = true;
            } else if (pageFormat.width == f.format.height && pageFormat.height == f.format.width) {
              paperSizeSelect.value = f.key;
              portraitCheckBox.removeAttribute('checked');
              portraitCheckBox.defaultChecked = false;
              portraitCheckBox.checked = false;
              landscapeCheckBox.setAttribute('checked', 'checked');
              landscapeCheckBox.defaultChecked = true;
              landscapeCheckBox.checked = true;
              detected = true;
            }
          }
        }

        // Selects custom format which is last in list
        if (!detected) {
          widthInput.value = pageFormat.width / 100;
          heightInput.value = pageFormat.height / 100;
          portraitCheckBox.setAttribute('checked', 'checked');
          paperSizeSelect.value = 'custom';
          formatDiv.style.display = 'none';
          customDiv.style.display = '';
        } else {
          formatDiv.style.display = '';
          customDiv.style.display = 'none';
        }
      }
    }

    listener();

    div.appendChild(paperSizeSelect);
    mxUtils.br(div);

    div.appendChild(formatDiv);
    div.appendChild(customDiv);

    var currentPageFormat = pageFormat;

    var update = function (evt, selectChanged) {
      var f = pf[paperSizeSelect.value];

      if (f.format != null) {
        widthInput.value = f.format.width / 100;
        heightInput.value = f.format.height / 100;
        customDiv.style.display = 'none';
        formatDiv.style.display = '';
      } else {
        formatDiv.style.display = 'none';
        customDiv.style.display = '';
      }

      var wi = parseFloat(widthInput.value);

      if (isNaN(wi) || wi <= 0) {
        widthInput.value = pageFormat.width / 100;
      }

      var hi = parseFloat(heightInput.value);

      if (isNaN(hi) || hi <= 0) {
        heightInput.value = pageFormat.height / 100;
      }

      var newPageFormat = new mxRectangle(
        0,
        0,
        Math.floor(parseFloat(widthInput.value) * 100),
        Math.floor(parseFloat(heightInput.value) * 100),
      );

      if (paperSizeSelect.value != 'custom' && landscapeCheckBox.checked) {
        newPageFormat = new mxRectangle(0, 0, newPageFormat.height, newPageFormat.width);
      }

      // Initial select of custom should not update page format to avoid update of combo
      if (
        (!selectChanged || !customSize) &&
        (newPageFormat.width != currentPageFormat.width ||
          newPageFormat.height != currentPageFormat.height)
      ) {
        currentPageFormat = newPageFormat;

        // Updates page format and reloads format panel
        if (pageFormatListener != null) {
          pageFormatListener(currentPageFormat);
        }
      }
    };

    mxEvent.addListener(portraitSpan, 'click', function (evt) {
      portraitCheckBox.checked = true;
      update(evt);
      mxEvent.consume(evt);
    });

    mxEvent.addListener(landscapeSpan, 'click', function (evt) {
      landscapeCheckBox.checked = true;
      update(evt);
      mxEvent.consume(evt);
    });

    mxEvent.addListener(widthInput, 'blur', update);
    mxEvent.addListener(widthInput, 'click', update);
    mxEvent.addListener(heightInput, 'blur', update);
    mxEvent.addListener(heightInput, 'click', update);
    mxEvent.addListener(landscapeCheckBox, 'change', update);
    mxEvent.addListener(portraitCheckBox, 'change', update);
    mxEvent.addListener(paperSizeSelect, 'change', function (evt) {
      // Handles special case where custom was chosen
      customSize = paperSizeSelect.value == 'custom';
      update(evt, true);
    });

    update();

    return {
      set: function (value) {
        pageFormat = value;
        listener(null, null, true);
      },
      get: function () {
        return currentPageFormat;
      },
      widthInput: widthInput,
      heightInput: heightInput,
    };
  };

  /**
   *
   */
  PageSetupDialog.getFormats = function () {
    return [
      {
        key: 'letter',
        title: 'US-Letter (8,5" x 11")',
        format: mxConstants.PAGE_FORMAT_LETTER_PORTRAIT,
      },
      { key: 'legal', title: 'US-Legal (8,5" x 14")', format: new mxRectangle(0, 0, 850, 1400) },
      {
        key: 'tabloid',
        title: 'US-Tabloid (11" x 17")',
        format: new mxRectangle(0, 0, 1100, 1700),
      },
      {
        key: 'executive',
        title: 'US-Executive (7" x 10")',
        format: new mxRectangle(0, 0, 700, 1000),
      },
      { key: 'a0', title: 'A0 (841 mm x 1189 mm)', format: new mxRectangle(0, 0, 3300, 4681) },
      { key: 'a1', title: 'A1 (594 mm x 841 mm)', format: new mxRectangle(0, 0, 2339, 3300) },
      { key: 'a2', title: 'A2 (420 mm x 594 mm)', format: new mxRectangle(0, 0, 1654, 2336) },
      { key: 'a3', title: 'A3 (297 mm x 420 mm)', format: new mxRectangle(0, 0, 1169, 1654) },
      { key: 'a4', title: 'A4 (210 mm x 297 mm)', format: mxConstants.PAGE_FORMAT_A4_PORTRAIT },
      { key: 'a5', title: 'A5 (148 mm x 210 mm)', format: new mxRectangle(0, 0, 583, 827) },
      { key: 'a6', title: 'A6 (105 mm x 148 mm)', format: new mxRectangle(0, 0, 413, 583) },
      { key: 'a7', title: 'A7 (74 mm x 105 mm)', format: new mxRectangle(0, 0, 291, 413) },
      { key: 'b4', title: 'B4 (250 mm x 353 mm)', format: new mxRectangle(0, 0, 980, 1390) },
      { key: 'b5', title: 'B5 (176 mm x 250 mm)', format: new mxRectangle(0, 0, 690, 980) },
      { key: '16-9', title: '16:9 (1600 x 900)', format: new mxRectangle(0, 0, 900, 1600) },
      { key: '16-10', title: '16:10 (1920 x 1200)', format: new mxRectangle(0, 0, 1200, 1920) },
      { key: '4-3', title: '4:3 (1600 x 1200)', format: new mxRectangle(0, 0, 1200, 1600) },
      { key: 'custom', title: mxResources.get('custom'), format: null },
    ];
  };

  /**
   * Constructs a new filename dialog.
   */
  var FilenameDialog = function (
    editorUi,
    filename,
    buttonText,
    fn,
    label,
    validateFn,
    content,
    helpLink,
    closeOnBtn,
    cancelFn,
    hints,
    w,
  ) {
    closeOnBtn = closeOnBtn != null ? closeOnBtn : true;
    var row, td;

    var table = document.createElement('table');
    var tbody = document.createElement('tbody');
    table.style.marginTop = '8px';

    row = document.createElement('tr');

    td = document.createElement('td');
    td.style.whiteSpace = 'nowrap';
    td.style.fontSize = '10pt';
    td.style.width = hints ? '80px' : '120px';
    mxUtils.write(td, (label || mxResources.get('filename')) + ':');

    row.appendChild(td);

    var nameInput = document.createElement('input');
    nameInput.setAttribute('value', filename || '');
    nameInput.style.marginLeft = '4px';
    nameInput.style.width = w != null ? w + 'px' : '180px';

    var genericBtn = mxUtils.button(buttonText, function () {
      if (validateFn == null || validateFn(nameInput.value)) {
        if (closeOnBtn) {
          editorUi.hideDialog();
        }

        fn(nameInput.value);
      }
    });
    genericBtn.className = 'geBtn gePrimaryBtn';

    this.init = function () {
      if (label == null && content != null) {
        return;
      }

      nameInput.focus();

      if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5 || mxClient.IS_QUIRKS) {
        nameInput.select();
      } else {
        document.execCommand('selectAll', false, null);
      }

      // Installs drag and drop handler for links
      if (Graph.fileSupport) {
        // Setup the dnd listeners
        var dlg = table.parentNode;

        if (dlg != null) {
          var graph = editorUi.editor.graph;
          var dropElt = null;

          mxEvent.addListener(dlg, 'dragleave', function (evt) {
            if (dropElt != null) {
              dropElt.style.backgroundColor = '';
              dropElt = null;
            }

            evt.stopPropagation();
            evt.preventDefault();
          });

          mxEvent.addListener(
            dlg,
            'dragover',
            mxUtils.bind(this, function (evt) {
              // IE 10 does not implement pointer-events so it can't have a drop highlight
              if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10)) {
                dropElt = nameInput;
                dropElt.style.backgroundColor = '#ebf2f9';
              }

              evt.stopPropagation();
              evt.preventDefault();
            }),
          );

          mxEvent.addListener(
            dlg,
            'drop',
            mxUtils.bind(this, function (evt) {
              if (dropElt != null) {
                dropElt.style.backgroundColor = '';
                dropElt = null;
              }

              if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0) {
                nameInput.value = decodeURIComponent(evt.dataTransfer.getData('text/uri-list'));
                genericBtn.click();
              }

              evt.stopPropagation();
              evt.preventDefault();
            }),
          );
        }
      }
    };

    td = document.createElement('td');
    td.style.whiteSpace = 'nowrap';
    td.appendChild(nameInput);
    row.appendChild(td);

    if (label != null || content == null) {
      tbody.appendChild(row);

      if (hints != null) {
        if (editorUi.editor.diagramFileTypes != null) {
          var typeSelect = FilenameDialog.createFileTypes(
            editorUi,
            nameInput,
            editorUi.editor.diagramFileTypes,
          );
          typeSelect.style.marginLeft = '6px';
          typeSelect.style.width = '74px';

          td.appendChild(typeSelect);
          nameInput.style.width = w != null ? w - 40 + 'px' : '140px';
        }

        td.appendChild(FilenameDialog.createTypeHint(editorUi, nameInput, hints));
      }
    }

    if (content != null) {
      row = document.createElement('tr');
      td = document.createElement('td');
      td.colSpan = 2;
      td.appendChild(content);
      row.appendChild(td);
      tbody.appendChild(row);
    }

    row = document.createElement('tr');
    td = document.createElement('td');
    td.colSpan = 2;
    td.style.paddingTop = '20px';
    td.style.whiteSpace = 'nowrap';
    td.setAttribute('align', 'right');

    var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
      editorUi.hideDialog();

      if (cancelFn != null) {
        cancelFn();
      }
    });
    cancelBtn.className = 'geBtn';

    if (editorUi.editor.cancelFirst) {
      td.appendChild(cancelBtn);
    }

    if (helpLink != null) {
      var helpBtn = mxUtils.button(mxResources.get('help'), function () {
        editorUi.editor.graph.openLink(helpLink);
      });

      helpBtn.className = 'geBtn';
      td.appendChild(helpBtn);
    }

    mxEvent.addListener(nameInput, 'keypress', function (e) {
      if (e.keyCode == 13) {
        genericBtn.click();
      }
    });

    td.appendChild(genericBtn);

    if (!editorUi.editor.cancelFirst) {
      td.appendChild(cancelBtn);
    }

    row.appendChild(td);
    tbody.appendChild(row);
    table.appendChild(tbody);

    this.container = table;
  };

  /**
   *
   */
  FilenameDialog.filenameHelpLink = null;

  /**
   *
   */
  FilenameDialog.createTypeHint = function (ui, nameInput, hints) {
    var hint = document.createElement('img');
    hint.style.cssText =
      'vertical-align:top;height:16px;width:16px;margin-left:4px;background-repeat:no-repeat;background-position:center bottom;cursor:pointer;';
    mxUtils.setOpacity(hint, 70);

    var nameChanged = function () {
      hint.setAttribute('src', Editor.helpImage);
      hint.setAttribute('title', mxResources.get('help'));

      for (var i = 0; i < hints.length; i++) {
        if (
          hints[i].ext.length > 0 &&
          nameInput.value
            .toLowerCase()
            .substring(nameInput.value.length - hints[i].ext.length - 1) ==
            '.' + hints[i].ext
        ) {
          hint.setAttribute('src', mxClient.imageBasePath + '/warning.png');
          hint.setAttribute('title', mxResources.get(hints[i].title));
          break;
        }
      }
    };

    mxEvent.addListener(nameInput, 'keyup', nameChanged);
    mxEvent.addListener(nameInput, 'change', nameChanged);
    mxEvent.addListener(hint, 'click', function (evt) {
      var title = hint.getAttribute('title');

      if (hint.getAttribute('src') == Editor.helpImage) {
        ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
      } else if (title != '') {
        ui.showError(
          null,
          title,
          mxResources.get('help'),
          function () {
            ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
          },
          null,
          mxResources.get('ok'),
          null,
          null,
          null,
          340,
          90,
        );
      }

      mxEvent.consume(evt);
    });

    nameChanged();

    return hint;
  };

  /**
   *
   */
  FilenameDialog.createFileTypes = function (editorUi, nameInput, types) {
    var typeSelect = document.createElement('select');

    for (var i = 0; i < types.length; i++) {
      var typeOption = document.createElement('option');
      typeOption.setAttribute('value', i);
      mxUtils.write(
        typeOption,
        mxResources.get(types[i].description) + ' (.' + types[i].extension + ')',
      );
      typeSelect.appendChild(typeOption);
    }

    mxEvent.addListener(typeSelect, 'change', function (evt) {
      var ext = types[typeSelect.value].extension;
      var idx = nameInput.value.lastIndexOf('.');

      if (idx > 0) {
        ext = types[typeSelect.value].extension;
        nameInput.value = nameInput.value.substring(0, idx + 1) + ext;
      } else {
        nameInput.value = nameInput.value + '.' + ext;
      }

      if ('createEvent' in document) {
        var changeEvent = document.createEvent('HTMLEvents');
        changeEvent.initEvent('change', false, true);
        nameInput.dispatchEvent(changeEvent);
      } else {
        nameInput.fireEvent('onchange');
      }
    });

    var nameInputChanged = function (evt) {
      var idx = nameInput.value.lastIndexOf('.');
      var active = 0;

      // Finds current extension
      if (idx > 0) {
        var ext = nameInput.value.toLowerCase().substring(idx + 1);

        for (var i = 0; i < types.length; i++) {
          if (ext == types[i].extension) {
            active = i;
            break;
          }
        }
      }

      typeSelect.value = active;
    };

    mxEvent.addListener(nameInput, 'change', nameInputChanged);
    mxEvent.addListener(nameInput, 'keyup', nameInputChanged);
    nameInputChanged();

    return typeSelect;
  };

  /**
   * Static overrides
   */
  (function () {
    // Uses HTML for background pages (to support grid background image)
    mxGraphView.prototype.validateBackgroundPage = function () {
      var graph = this.graph;

      if (graph.container != null && !graph.transparentBackground) {
        if (graph.pageVisible) {
          var bounds = this.getBackgroundPageBounds();

          if (this.backgroundPageShape == null) {
            // Finds first element in graph container
            var firstChild = graph.container.firstChild;

            while (firstChild != null && firstChild.nodeType != mxConstants.NODETYPE_ELEMENT) {
              firstChild = firstChild.nextSibling;
            }

            if (firstChild != null) {
              this.backgroundPageShape = this.createBackgroundPageShape(bounds);
              this.backgroundPageShape.scale = 1;

              // Shadow filter causes problems in outline window in quirks mode. IE8 standards
              // also has known rendering issues inside mxWindow but not using shadow is worse.
              this.backgroundPageShape.isShadow = !mxClient.IS_QUIRKS;
              this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
              this.backgroundPageShape.init(graph.container);

              // Required for the browser to render the background page in correct order
              firstChild.style.position = 'absolute';
              graph.container.insertBefore(this.backgroundPageShape.node, firstChild);
              this.backgroundPageShape.redraw();

              this.backgroundPageShape.node.className = 'geBackgroundPage';

              // Adds listener for double click handling on background
              mxEvent.addListener(
                this.backgroundPageShape.node,
                'dblclick',
                mxUtils.bind(this, function (evt) {
                  graph.dblClick(evt);
                }),
              );

              // Adds basic listeners for graph event dispatching outside of the
              // container and finishing the handling of a single gesture
              mxEvent.addGestureListeners(
                this.backgroundPageShape.node,
                mxUtils.bind(this, function (evt) {
                  graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
                }),
                mxUtils.bind(this, function (evt) {
                  // Hides the tooltip if mouse is outside container
                  if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) {
                    graph.tooltipHandler.hide();
                  }

                  if (graph.isMouseDown && !mxEvent.isConsumed(evt)) {
                    graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
                  }
                }),
                mxUtils.bind(this, function (evt) {
                  graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
                }),
              );
            }
          } else {
            this.backgroundPageShape.scale = 1;
            this.backgroundPageShape.bounds = bounds;
            this.backgroundPageShape.redraw();
          }
        } else if (this.backgroundPageShape != null) {
          this.backgroundPageShape.destroy();
          this.backgroundPageShape = null;
        }

        this.validateBackgroundStyles();
      }
    };

    // Updates the CSS of the background to draw the grid
    mxGraphView.prototype.validateBackgroundStyles = function () {
      var graph = this.graph;
      var color =
        graph.background == null || graph.background == mxConstants.NONE
          ? graph.defaultPageBackgroundColor
          : graph.background;
      var gridColor =
        color != null && this.gridColor != color.toLowerCase() ? this.gridColor : '#ffffff';
      var image = 'none';
      var position = '';

      if (graph.isGridEnabled()) {
        var phase = 10;

        if (mxClient.IS_SVG) {
          // Generates the SVG required for drawing the dynamic grid
          image = unescape(encodeURIComponent(this.createSvgGrid(gridColor)));
          image = window.btoa ? btoa(image) : Base64.encode(image, true);
          image = 'url(' + 'data:image/svg+xml;base64,' + image + ')';
          phase = graph.gridSize * this.scale * this.gridSteps;
        } else {
          // Fallback to grid wallpaper with fixed size
          image = 'url(' + this.gridImage + ')';
        }

        var x0 = 0;
        var y0 = 0;

        if (graph.view.backgroundPageShape != null) {
          var bds = this.getBackgroundPageBounds();

          x0 = 1 + bds.x;
          y0 = 1 + bds.y;
        }

        // Computes the offset to maintain origin for grid
        position =
          -Math.round(phase - mxUtils.mod(this.translate.x * this.scale - x0, phase)) +
          'px ' +
          -Math.round(phase - mxUtils.mod(this.translate.y * this.scale - y0, phase)) +
          'px';
      }

      var canvas = graph.view.canvas;

      if (canvas.ownerSVGElement != null) {
        canvas = canvas.ownerSVGElement;
      }

      if (graph.view.backgroundPageShape != null) {
        graph.view.backgroundPageShape.node.style.backgroundPosition = position;
        graph.view.backgroundPageShape.node.style.backgroundImage = image;
        graph.view.backgroundPageShape.node.style.backgroundColor = color;
        graph.container.className = 'geDiagramContainer geDiagramBackdrop';
        canvas.style.backgroundImage = 'none';
        canvas.style.backgroundColor = '';
      } else {
        graph.container.className = 'geDiagramContainer';
        canvas.style.backgroundPosition = position;
        canvas.style.backgroundColor = color;
        canvas.style.backgroundImage = image;
      }
    };

    // Returns the SVG required for painting the background grid.
    mxGraphView.prototype.createSvgGrid = function (color) {
      var tmp = this.graph.gridSize * this.scale;

      while (tmp < this.minGridSize) {
        tmp *= 2;
      }

      var tmp2 = this.gridSteps * tmp;

      // Small grid lines
      var d = [];

      for (var i = 1; i < this.gridSteps; i++) {
        var tmp3 = i * tmp;
        d.push(
          'M 0 ' + tmp3 + ' L ' + tmp2 + ' ' + tmp3 + ' M ' + tmp3 + ' 0 L ' + tmp3 + ' ' + tmp2,
        );
      }

      // KNOWN: Rounding errors for certain scales (eg. 144%, 121% in Chrome, FF and Safari). Workaround
      // in Chrome is to use 100% for the svg size, but this results in blurred grid for large diagrams.
      var size = tmp2;
      var svg =
        '<svg width="' +
        size +
        '" height="' +
        size +
        '" xmlns="' +
        mxConstants.NS_SVG +
        '">' +
        '<defs><pattern id="grid" width="' +
        tmp2 +
        '" height="' +
        tmp2 +
        '" patternUnits="userSpaceOnUse">' +
        '<path d="' +
        d.join(' ') +
        '" fill="none" stroke="' +
        color +
        '" opacity="0.2" stroke-width="1"/>' +
        '<path d="M ' +
        tmp2 +
        ' 0 L 0 0 0 ' +
        tmp2 +
        '" fill="none" stroke="' +
        color +
        '" stroke-width="1"/>' +
        '</pattern></defs><rect width="100%" height="100%" fill="url(#grid)"/></svg>';

      return svg;
    };

    // Adds panning for the grid with no page view and disabled scrollbars
    var mxGraphPanGraph = mxGraph.prototype.panGraph;
    mxGraph.prototype.panGraph = function (dx, dy) {
      mxGraphPanGraph.apply(this, arguments);

      if (this.shiftPreview1 != null) {
        var canvas = this.view.canvas;

        if (canvas.ownerSVGElement != null) {
          canvas = canvas.ownerSVGElement;
        }

        var phase = this.gridSize * this.view.scale * this.view.gridSteps;
        var position =
          -Math.round(phase - mxUtils.mod(this.view.translate.x * this.view.scale + dx, phase)) +
          'px ' +
          -Math.round(phase - mxUtils.mod(this.view.translate.y * this.view.scale + dy, phase)) +
          'px';
        canvas.style.backgroundPosition = position;
      }
    };

    // Draws page breaks only within the page
    mxGraph.prototype.updatePageBreaks = function (visible, width, height) {
      var scale = this.view.scale;
      var tr = this.view.translate;
      var fmt = this.pageFormat;
      var ps = scale * this.pageScale;

      var bounds2 = this.view.getBackgroundPageBounds();

      width = bounds2.width;
      height = bounds2.height;
      var bounds = new mxRectangle(scale * tr.x, scale * tr.y, fmt.width * ps, fmt.height * ps);

      // Does not show page breaks if the scale is too small
      visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;

      var horizontalCount = visible ? Math.ceil(height / bounds.height) - 1 : 0;
      var verticalCount = visible ? Math.ceil(width / bounds.width) - 1 : 0;
      var right = bounds2.x + width;
      var bottom = bounds2.y + height;

      if (this.horizontalPageBreaks == null && horizontalCount > 0) {
        this.horizontalPageBreaks = [];
      }

      if (this.verticalPageBreaks == null && verticalCount > 0) {
        this.verticalPageBreaks = [];
      }

      var drawPageBreaks = mxUtils.bind(this, function (breaks) {
        if (breaks != null) {
          var count = breaks == this.horizontalPageBreaks ? horizontalCount : verticalCount;

          for (var i = 0; i <= count; i++) {
            var pts =
              breaks == this.horizontalPageBreaks
                ? [
                    new mxPoint(
                      Math.round(bounds2.x),
                      Math.round(bounds2.y + (i + 1) * bounds.height),
                    ),
                    new mxPoint(Math.round(right), Math.round(bounds2.y + (i + 1) * bounds.height)),
                  ]
                : [
                    new mxPoint(
                      Math.round(bounds2.x + (i + 1) * bounds.width),
                      Math.round(bounds2.y),
                    ),
                    new mxPoint(Math.round(bounds2.x + (i + 1) * bounds.width), Math.round(bottom)),
                  ];

            if (breaks[i] != null) {
              breaks[i].points = pts;
              breaks[i].redraw();
            } else {
              var pageBreak = new mxPolyline(pts, this.pageBreakColor);
              pageBreak.dialect = this.dialect;
              pageBreak.isDashed = this.pageBreakDashed;
              pageBreak.pointerEvents = false;
              pageBreak.init(this.view.backgroundPane);
              pageBreak.redraw();

              breaks[i] = pageBreak;
            }
          }

          for (var i = count; i < breaks.length; i++) {
            breaks[i].destroy();
          }

          breaks.splice(count, breaks.length - count);
        }
      });

      drawPageBreaks(this.horizontalPageBreaks);
      drawPageBreaks(this.verticalPageBreaks);
    };

    // Disables removing relative children and table rows and cells from parents
    var mxGraphHandlerShouldRemoveCellsFromParent =
      mxGraphHandler.prototype.shouldRemoveCellsFromParent;
    mxGraphHandler.prototype.shouldRemoveCellsFromParent = function (parent, cells, evt) {
      for (var i = 0; i < cells.length; i++) {
        if (this.graph.isTableCell(cells[i]) || this.graph.isTableRow(cells[i])) {
          return false;
        } else if (this.graph.getModel().isVertex(cells[i])) {
          var geo = this.graph.getCellGeometry(cells[i]);

          if (geo != null && geo.relative) {
            return false;
          }
        }
      }

      return mxGraphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
    };

    // Overrides to ignore hotspot only for target terminal
    var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
    mxConnectionHandler.prototype.createMarker = function () {
      var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);

      marker.intersects = mxUtils.bind(this, function (state, evt) {
        if (this.isConnecting()) {
          return true;
        }

        return mxCellMarker.prototype.intersects.apply(marker, arguments);
      });

      return marker;
    };

    // Creates background page shape
    mxGraphView.prototype.createBackgroundPageShape = function (bounds) {
      return new mxRectangleShape(bounds, '#ffffff', this.graph.defaultPageBorderColor);
    };

    // Fits the number of background pages to the graph
    mxGraphView.prototype.getBackgroundPageBounds = function () {
      var gb = this.getGraphBounds();

      // Computes unscaled, untranslated graph bounds
      var x = gb.width > 0 ? gb.x / this.scale - this.translate.x : 0;
      var y = gb.height > 0 ? gb.y / this.scale - this.translate.y : 0;
      var w = gb.width / this.scale;
      var h = gb.height / this.scale;

      var fmt = this.graph.pageFormat;
      var ps = this.graph.pageScale;

      var pw = fmt.width * ps;
      var ph = fmt.height * ps;

      var x0 = Math.floor(Math.min(0, x) / pw);
      var y0 = Math.floor(Math.min(0, y) / ph);
      var xe = Math.ceil(Math.max(1, x + w) / pw);
      var ye = Math.ceil(Math.max(1, y + h) / ph);

      var rows = xe - x0;
      var cols = ye - y0;

      var bounds = new mxRectangle(
        this.scale * (this.translate.x + x0 * pw),
        this.scale * (this.translate.y + y0 * ph),
        this.scale * rows * pw,
        this.scale * cols * ph,
      );

      return bounds;
    };

    // Add panning for background page in VML
    var graphPanGraph = mxGraph.prototype.panGraph;
    mxGraph.prototype.panGraph = function (dx, dy) {
      graphPanGraph.apply(this, arguments);

      if (
        this.dialect != mxConstants.DIALECT_SVG &&
        this.view.backgroundPageShape != null &&
        (!this.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.container))
      ) {
        this.view.backgroundPageShape.node.style.marginLeft = dx + 'px';
        this.view.backgroundPageShape.node.style.marginTop = dy + 'px';
      }
    };

    /**
     * Consumes click events for disabled menu items.
     */
    var mxPopupMenuAddItem = mxPopupMenu.prototype.addItem;
    mxPopupMenu.prototype.addItem = function (title, image, funct, parent, iconCls, enabled) {
      var result = mxPopupMenuAddItem.apply(this, arguments);

      if (enabled != null && !enabled) {
        mxEvent.addListener(result, 'mousedown', function (evt) {
          mxEvent.consume(evt);
        });
      }

      return result;
    };

    /**
     * Selects tables before cells and rows.
     */
    var mxGraphHandlerIsPropagateSelectionCell = mxGraphHandler.prototype.isPropagateSelectionCell;
    mxGraphHandler.prototype.isPropagateSelectionCell = function (cell, immediate, me) {
      var result = false;
      var parent = this.graph.model.getParent(cell);

      if (immediate) {
        var geo = this.graph.model.isEdge(cell) ? null : this.graph.getCellGeometry(cell);

        result =
          !this.graph.model.isEdge(parent) &&
          !this.graph.isSiblingSelected(cell) &&
          ((geo != null && geo.relative) ||
            !this.graph.isContainer(parent) ||
            this.graph.isPart(cell));
      } else {
        result = mxGraphHandlerIsPropagateSelectionCell.apply(this, arguments);

        if (this.graph.isTableCell(cell) || this.graph.isTableRow(cell)) {
          var table = parent;

          if (!this.graph.isTable(table)) {
            table = this.graph.model.getParent(table);
          }

          result =
            !this.graph.selectionCellsHandler.isHandled(table) ||
            (this.graph.isCellSelected(table) && this.graph.isToggleEvent(me.getEvent())) ||
            (this.graph.isCellSelected(cell) && !this.graph.isToggleEvent(me.getEvent())) ||
            (this.graph.isTableCell(cell) && this.graph.isCellSelected(parent));
        }
      }

      return result;
    };

    /**
     * Returns last selected ancestor
     */
    mxPopupMenuHandler.prototype.getCellForPopupEvent = function (me) {
      var cell = me.getCell();
      var model = this.graph.getModel();
      var parent = model.getParent(cell);
      var state = this.graph.view.getState(parent);
      var selected = this.graph.isCellSelected(cell);

      while (state != null && (model.isVertex(parent) || model.isEdge(parent))) {
        var temp = this.graph.isCellSelected(parent);
        selected = selected || temp;

        if (temp || (!selected && (this.graph.isTableCell(cell) || this.graph.isTableRow(cell)))) {
          cell = parent;
        }

        parent = model.getParent(parent);
      }

      return cell;
    };
  })();
  return { Editor, Graph, HoverIcons, Dialog, PageSetupDialog,PrintDialog };
}
